﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Controls.Base;
using Silvermoon.Visuals;
using System.Drawing;
using Silvermoon.Images;
using Silvermoon.Animations;
using Silvermoon.Core;
using Silvermoon.Utils;
using Silvermoon.Controls.Interfaces;

namespace Silvermoon.Controls
{
    /// <summary>
    /// Control to show hierarchical collections of <see cref="T:MenuItem"/>s.
    /// </summary>
    public class Menu : ListViewBase, IForm
    {
        /// <summary>
        /// Specifies the default duration for menu hide/show transition.
        /// </summary>
        public static readonly int DefaultDuration = 350;

        const int arrowSize = 24;
        const int ButtonHeight = 54;
        const int ShadowSize = 3;
        const int MarginSize = 12;
        new const int BorderSize = 2;
        #region Fields

        public readonly int CornerRadius = Control.Scale(24);

        private bool hiding;
        private Menu parentMenu;
        private BackgroundVisual background;
        private BackgroundVisual upButton;
        private BackgroundVisual downButton;
        private ImageVisual upImage;
        private ImageVisual downImage;
        private bool hasButtons;
        private bool isUpEnabled;
        private bool isDownEnabled = true;
        private List<Visual> visuals = new List<Visual>(4);

        #endregion
        #region ctor/dtor

        public Menu()
            : base()
        {
            background = new BackgroundVisual { Texture = SysImages.MenuBackground };
            Visuals = visuals;
            visuals.Add(background);
            background.Colors = new Color[] { Color.White };
            ItemsMargin = Control.ScaleMargin(MarginSize, MarginSize, MarginSize, MarginSize);
            Clipping = Control.ScaleMargin(MarginSize, BorderSize + 1, MarginSize + ShadowSize, BorderSize + ShadowSize + 1);
            TextColor = SysColors.MenuTextColor;
            //            this.Colo = SysColors.MenuColor;
            Visible = false;
            HideTransition = TransitionMode.FadeOut;
        }


        internal Menu(Menu parentMenu)
            : this()
        {
            this.parentMenu = parentMenu;
        }

        public override void Dispose()
        {
            if (upButton != null) upButton.Dispose();
            if (downButton != null) downButton.Dispose();
            if (upImage != null) upImage.Dispose();
            if (downImage != null) downImage.Dispose();
            SubControls.Clear();
            Visuals = null;
            background.Dispose();
            background = null;
            base.Dispose();
        }

        #endregion
        #region Properties


        protected bool IsUpButtonEnabled { get { return isUpEnabled; } set { if (isUpEnabled != value) { isUpEnabled = value; Invalidate(InvalidationFlag.Enabled); } } }
        protected bool IsDownButtonEnabled { get { return isDownEnabled; } set { if (isDownEnabled != value) { isDownEnabled = value; Invalidate(InvalidationFlag.Enabled); } } }

        public TransitionMode HideTransition { get; set; }


        private Menu OpenedSubMenu { get; set; }

        /// <summary>
        /// Gets wether the menu, one of it's items or any descendant menu has the focus.
        /// </summary>
        public override bool HasFocus
        {
            get
            {
                bool result = base.HasFocus;
                if (!result && OpenedSubMenu != null) result = OpenedSubMenu.HasFocus;
                return result;
            }
        }

        #endregion
        #region Methods

        /// <summary>
        /// Don't let the content scroll unless it does not fit completely into the menu:
        /// </summary>
        protected override bool CanScroll
        {
            get
            {
                return TotalHeight > ClientHeight;
            }
        }

        internal protected override ItemBase OnAttachItem(int index)
        {
            var item = Items[index];
            Width = Math.Max(Width, item.Width + 32);
            return base.OnAttachItem(index);
        }

        private void DetermineSize()
        {
            if (CheckFlag(InvalidationFlag.Size))
            {
                int w = Width;
                if (w == 0)
                {
                    foreach (var item in Items)
                    {
                        item.PreLoad();
                        w = Math.Max(item.Width, w);
                    }
                }
                int h = TotalHeight + ShadowSize + MarginSize * 2;
                int hmax = Control.UnscaleY(Screen.Height - 40);
                if (Height != 0 && Height < hmax) hmax = Height;
                hasButtons = ShowButtons(h > hmax);
                int bh = ButtonHeight;
                if (hasButtons)
                {
                    h = hmax;
                    Clipping = new Margin(MarginSize, BorderSize + 1 + bh, MarginSize + ShadowSize, BorderSize + ShadowSize + 1 + bh);
                }
                else
                {
                    Clipping = new Margin(MarginSize, BorderSize + 1, MarginSize + ShadowSize, BorderSize + ShadowSize + 1);
                }
                if (h < background.Texture.Height) h = background.Texture.Height;
                Size = new Size(w, h);
            }
        }

        protected override void OnSizeChanged()
        {
            base.OnSizeChanged();
        }

        protected virtual bool ShowButtons(bool show)
        {
            return show;
        }

        protected override void PreLayout()
        {
            DetermineSize();
            base.PreLayout();
        }

        protected override void OnLayout()
        {
            background.Alpha = Alpha;
            int bh = Control.ScaleY(ButtonHeight);
            if (CheckFlag(InvalidationFlag.Size))
            {
                visuals.Clear();
                visuals.Add(background);
                if (!hasButtons)
                {
                    background.Bounds = Control.ScaledRect(0, 0, Width, Height);
                    ItemsMargin = Control.ScaleMargin(MarginSize, MarginSize, MarginSize, MarginSize);
                }
                else
                {
                    Color topColor = GetButtonColor(isUpEnabled);
                    Color bottomColor = GetButtonColor(isDownEnabled);
                    if (upButton == null) upButton = new BackgroundVisual { Colors = ColorUtil.CreateGradientColor(topColor), Texture = SysImages.RoundAtTop };
                    if (downButton == null) downButton = new BackgroundVisual { Colors = ColorUtil.CreateGradientColor(bottomColor), Texture = SysImages.RoundAtBottom };
                    if (upImage == null) upImage = new ImageVisual { Image = SysImages.UpImage };
                    if (downImage == null) downImage = new ImageVisual { Image = SysImages.DownImage };
                    upButton.Bounds = GetUpButtonBounds();
                    downButton.Bounds = GetDownButtonBounds();
                    background.Bounds = Control.ScaledRect(0, 0, Width, Height);
                    ItemsMargin = Control.ScaleMargin(MarginSize, MarginSize + bh, MarginSize, MarginSize + 0 * bh);
                    int arrowH = arrowSize * 2 / 4;
                    upImage.Bounds = Control.ScaledRect((Width - arrowSize) / 2, (ButtonHeight - arrowH) / 2, arrowSize, arrowH);
                    downImage.Bounds = Control.ScaledRect((Width - arrowSize) / 2, Height - ButtonHeight - ShadowSize + (ButtonHeight - arrowH) / 2, arrowSize, arrowH);
                    visuals.Add(upButton);
                    visuals.Add(downButton);
                    visuals.Add(upImage);
                    visuals.Add(downImage);
                }
            }
            if (CheckFlag(InvalidationFlag.Enabled))
            {
                if (upImage != null)
                {
                    upImage.Color = GetIconColor(isUpEnabled);
                    upImage.Alpha = isUpEnabled ? 240 : 80;
                }
                if (downImage != null)
                {
                    downImage.Color = GetIconColor(isDownEnabled);
                    downImage.Alpha = isDownEnabled ? 240 : 80;
                }
                if (upButton != null)
                {
                    upButton.Colors = ColorUtil.CreatePlasticColors(GetButtonColor(isUpEnabled));
                }
                if (downButton != null) downButton.Colors = ColorUtil.CreatePlasticColors(GetButtonColor(isDownEnabled));
            }
            base.OnLayout();
        }

        protected Rectangle GetUpButtonBounds()
        {
            return Control.ScaledRect(0, 0, Width - ShadowSize, ButtonHeight);
        }

        protected Rectangle GetDownButtonBounds()
        {
            return Control.ScaledRect(0, Height - ButtonHeight - ShadowSize, Width - ShadowSize, ButtonHeight);
        }

        protected override void OnTopOffsetChanged(int oldOffset, int newOffset)
        {
            base.OnTopOffsetChanged(oldOffset, newOffset);
            IsUpButtonEnabled = TopOffset > MarginRect.Top;
            IsDownButtonEnabled = (TopOffset + MarginRect.Bottom) < TotalHeight;

        }

        protected virtual Color GetIconColor(bool isEnabled)
        {
            return isEnabled ? Color.DarkSlateBlue : Color.SlateGray;
        }

        protected virtual Color GetButtonColor(bool isEnabled)
        {
            return isEnabled ? SysColors.ButtonColor : Color.Silver;
        }

        public MenuItem AddItem(string text)
        {
            MenuItem item = new MenuItem { Text = text };
            Items.Add(item);
            return item;
        }

        public MenuItem AddItem(string text, bool isChecked)
        {
            MenuItem item = new MenuItem { Text = text, Checked = isChecked };
            Items.Add(item);
            return item;
        }

        public ItemSeparator AddSeparator()
        {
            ItemSeparator separator = new ItemSeparator();
            Items.Add(separator);
            return separator;
        }

        protected override void OnItemsChanged()
        {
            Invalidate(InvalidationFlag.Size | InvalidationFlag.LayoutAppearance);
            base.OnItemsChanged();
        }

        public virtual void Show()
        {
            Show(TransitionMode.SwipeInFromBottom | TransitionMode.FadeIn);
        }

        private AnimEventArgs transition;

        private void StopTransition()
        {
            if (transition != null) transition.Abort();
        }

        public virtual void Show(TransitionMode mode)
        {
            if (!Visible)
            {
                Visible = false;
                StopTransition();
                Invalidate(InvalidationFlag.Size);
                SetSelectedItem();
                OnShowing();
                hiding = false;
                Visible = true;
                TopOffset = 0;
                transition = Transition.InvokeAsync(this, mode, Menu.DefaultDuration, 0, (s, e) => { OnShown(); });
                Focus();
            }
        }

        protected virtual void SetSelectedItem()
        {
            SelectedItem = ItemCount > 0 ? GetItem(0) : null;
        }

        public virtual void Hide()
        {
            Hide(HideTransition);
        }

        public virtual void Hide(TransitionMode mode)
        {
            if (Visible && !hiding)
            {
                StopTransition();
                Screen.MouseFocusChanged -= OnScreenFocusChanged;
                hiding = true;
                OnHiding();
                if (IsFocused && parentMenu != null && parentMenu.Visible && !parentMenu.IsAnimated) parentMenu.Focus();
                transition = Transition.InvokeAsync(this, mode, Menu.DefaultDuration, 25, (s, e) => { Screen.Detach(this); hiding = false; OnHidden(); });
            }
        }

        public void Close()
        {
            SelectedItem = null;
            Hide();
            if (parentMenu != null) parentMenu.Close();
        }

        public override void OnItemClick(ItemBase item)
        {
            base.OnItemClick(item);
            if (parentMenu != null) parentMenu.Hide();
        }

        protected override void OnVisibleChanged()
        {
            base.OnVisibleChanged();
            if (Created)
            {
                if (Visible)
                {
                    if (parentMenu != null) parentMenu.OpenedSubMenu = this;
                    Screen.Attach(this);
                    Screen.MouseFocusChanged -= OnScreenFocusChanged;
                    Screen.MouseFocusChanged += OnScreenFocusChanged;
                }
                else
                {
                    if (parentMenu != null)
                    {
                        if (parentMenu.OpenedSubMenu == this)
                        {
                            parentMenu.OpenedSubMenu = null;
                        }
                    }
                    Screen.MouseFocusChanged -= OnScreenFocusChanged;
                    Screen.Detach(this);
                }
            }
        }

        void OnScreenFocusChanged(object sender, EventArgs e)
        {
            if (Screen.MessageServer.MouseFocusControl != null)
            {
                if (!this.HasFocus) Hide();
            }
        }

        public override void OnKeyDown(System.Windows.Forms.KeyEventArgs e)
        {
            if (!e.Handled)
            {
                switch (e.KeyCode)
                {
                    case System.Windows.Forms.Keys.Left:
                        e.Handled = true;
                        if (parentMenu != null) parentMenu.Focus();
                        Hide();
                        break;

                    case System.Windows.Forms.Keys.Right:
                        MenuItem item = SelectedItem as MenuItem;
                        if (item != null && item.HasItems)
                        {
                            e.Handled = true;
                            item.OpenSubMenu();
                        }
                        break;
                }
            }
            base.OnKeyDown(e);
        }

        public override void OnMouseUp(MouseUpEventArgs e)
        {
            base.OnMouseUp(e);
            Point cp = Control.Scale(e.ScreenToClient(e.DownPosition));
            if (TopOffset > 0)
            {
                Rectangle rTop = GetUpButtonBounds();
                if (rTop.Contains(cp))
                {
                    ScrollTo(0);

                    e.Handled = true;
                }
            }
            if (isDownEnabled)
            {
                Rectangle rTop = GetDownButtonBounds();
                if (rTop.Contains(cp))
                {
                    int top = ClientTop;
                    int height = MarginRect.Height;
                    ScrollTo(TotalHeight - height - top);
                    e.Handled = true;
                }
            }
        }

        protected virtual void OnHidden()
        {
            if (Hidden != null) Hidden(this, EventArgs.Empty);
        }

        protected virtual void OnShown()
        {
            if (Shown != null) Shown(this, EventArgs.Empty);
        }

        protected virtual void OnHiding()
        {
            if (Hiding != null) Hiding(this, EventArgs.Empty);
        }

        protected virtual void OnShowing()
        {
            if (Showing != null) Showing(this, EventArgs.Empty);
        }

        #endregion
        #region events

        public event EventHandler Hidden;
        public event EventHandler Shown;

        public event EventHandler Hiding;
        public event EventHandler Showing;
        #endregion



    }
}
